﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Drawing;
using System.IO;
using System.Drawing.Imaging;

/**
 * メモ：RGBA5551などのパック形式の画像は”そのまま”保存してGPUに転送する。
 * つまり4バイト/ピクセル使用して保存する。
 * */



namespace M3G {
    public class Image2D : ImageBase {
        #region Field
        #endregion

        #region Method
        /**
         * 白紙の2D画像を造るコンストラクター
         * */
        public Image2D (int format, int width, int height)
            : base (format, width, height) {
            AllocateImage (0);
        }

        /**
         * 指定された画像データの2D画像を作るコンストラクター
         * */
        public Image2D (int format, int width, int height, byte[] image)
            : this (format, width, height) {
            if (image == null) {
                throw new ArgumentNullException ("Image is null");
            }
            var required = Width * Height * Bpp;
            if (image.Length < required) {
                throw new ArgumentException ("Length of Imagae is less than required, req=" + required);
            }

            SetPixels (0, 0, image);
            Commit ();
        }

        /**
         *  指定されたミップマップピラミッドを持つ画像データの2D画像を作るコンストラクター
         * */
        public Image2D (int format, int width, int height, byte[][] image)
            : this (format, width, height) {
            if (image == null) {
                throw new ArgumentNullException ("Image is null");
            }
            var requiredMipmap = MaxMipLevel + 1;
            if (image.Length < requiredMipmap) {
                throw new ArgumentException ("Mipmap hierarchy is less than required, len=" + image.Length + ",req=" + requiredMipmap);
            }
            for (var m=0; m <= MaxMipLevel; m++) {
                var requiredLength = MiplevelWidth(m) * MiplevelHeight(m) * Bpp;
                if (image[m].Length < requiredLength) {
                    throw new ArgumentException ("Length of Mipmap[i] is less than required, len=" + image[m].Length + ",req=" + requiredLength);
                }
            }

            for (var m=0; m <= MaxMipLevel; m++) {
                SetPixels (0, m, image[m]);
            }
            Commit ();
        }

        /**
         * .NetのBitmapオブジェクトから2D画像を作るコンストラクター
         * */
        public Image2D (int format, Bitmap image)
            : this (format, (image == null) ? 0 : image.Width, (image == null) ? 0 : image.Height) {
            if (image == null) {
                throw new ArgumentNullException ("Image is null");
            }

            SetPixels (0, 0, ConvertToPixels (Format, image));
            Commit ();
        }

    

        /**
         * パレット化2D画像を作るコンストラクター。未対応
         * */
        public Image2D (int format, int width, int height, byte[][] image, byte[] palette)
            : this (format, width, height) {
            throw new NotImplementedException ("Sorry");
        }

        /**
         * パレット化2D画像を作るコンストラクター。未対応
         * */
        public Image2D (int format, int width, int height, byte[] image, byte[] palette)
            : this (format, width, height) {
            throw new NotImplementedException ("Sorry");
        }

        /**
         * コピーコンストラクター
         * */
        protected Image2D (Image2D image)
            : base (image) {
        }

        /**
         * 画像ピクセルを書き換える。
         * M3G-1.1の関数
         * */
        public void Set (int x, int y, int width, int height, byte[] image) {
            if (image == null) {
                throw new ArgumentNullException ("Image is null");
            }
            if (Mutable == false) {
                throw new InvalidOperationException ("This Image2D is immutable");
            }
            if (x < 0 || x + width > Width || y < 0 || y + height > Height) {
                throw new ArgumentException ("Size is invalid, x=" + x + ",y=" + y + ",width=" + width + ",height=" + height);
            }
            var required = width * height * Bpp;
            if (image.Length < required) {
                throw new ArgumentException ("Length of image is less than required, len=" + image.Length + ",req=" + required);
            }

            SetPixels (0, 0, x, y, width, height, image);
        }

        /**
         * 画像の1部を書き換える。
         * byte[]で指定
         * 注意:パック形式の画像も必ず１コンポーネント１バイトで指定する。
         * */
        public void Set (int miplevel, int x, int y, int width, int height, byte[] image) {
            if (image == null) {
                throw new ArgumentNullException ("Image is null");
            }
            if (Mutable == false) {
                throw new InvalidOperationException ("This Image2D is immutable");
            }
            if ((Option & NoMipmap) != 0 && miplevel != 0) {
                throw new ArgumentException ("MipmaplLevel is specified to NoMipmap image");
            }
            if (miplevel < 0 || miplevel > MaxMipLevel) {
                throw new ArgumentException ("Miplevel must be within [0," + MaxMipLevel + "]");
            }
            if (x < 0 || x + width > Width || y < 0 || y + height > Height) {
                throw new ArgumentException ("Size is invalid, x=" + x + ",y=" + y + ",width=" + width + ",height=" + height);
            }
            var required = width * height * Bpp;
            if (image.Length < required) {
                throw new ArgumentException ("Length of image is less than required, len=" + image.Length + ",req=" + required);
            }

            SetPixels (0, miplevel, x, y, width, height, image);
        }

        /**
         * 画像の1部を書き換える。
         * ushort[]で指定
         * */
        public void Set (int miplevel, int x, int y, int width, int height, ushort[] image) {
            if (image == null) {
                throw new ArgumentNullException ("Image is null");
            }
            if (Mutable == false) {
                throw new InvalidOperationException ("This Image2D is immutable");
            }
            if (Format < Image2D.RGB565 || Format > Image2D.RGBA4444) {
                throw new ArgumentException ("Format is not compatible with ushort API");
            }
            if ((Option & NoMipmap) != 0 && miplevel != 0) {
                throw new ArgumentException ("MipmaplLevel is specified to NoMipmap image");
            }
            if (miplevel < 0 || miplevel > MaxMipLevel) {
                throw new ArgumentException ("Miplevel must be within [0," + MaxMipLevel + "]");
            }
            if (x < 0 || x + width > Width || y < 0 || y + height > Height) {
                throw new ArgumentException ("Size is invalid, x=" + x + ",y=" + y + ",width=" + width + ",height=" + height);
            }
            var required = width * height;
            if (image.Length < required) {
                throw new ArgumentException ("Length of image is less than required, len=" + image.Length + ",req=" + required);
            }

            var pixels = ConvertToPixels (Format, image);

            SetPixels (0, miplevel, x, y, width, height, pixels);
        }

        /**
         * 画像の1部を書き換える。
         * uint[]で指定
         * */
        public void Set (int miplevel, int x, int y, int width, int height, uint[] image) {
            if (image == null) {
                throw new ArgumentNullException ("Image is null");
            }
            if (Mutable == false) {
                throw new InvalidOperationException ("This Image2D is immutable");
            }
            if ((Option & NoMipmap) != 0 && miplevel != 0) {
                throw new ArgumentException ("MipmaplLevel is specified to NoMipmap image");
            }
            if (miplevel < 0 || miplevel > MaxMipLevel) {
                throw new ArgumentException ("Miplevel must be within [0," + MaxMipLevel + "]");
            }
            if (x < 0 || x + width > Width || y < 0 || y + height > Height) {
                throw new ArgumentException ("Size is invalid, x=" + x + ",y=" + y + ",width=" + width + ",height=" + height);
            }
            var required = width * height;
            if (image.Length < required) {
                throw new ArgumentException ("Length of image is less than required, len=" + image.Length + ",req=" + required);
            }

            var pixels = ConvertToPixels (Format, image);

            SetPixels (0, miplevel, x, y, width, height, pixels);
        }

        /**
         * 画像の1部を書き換える。
         * Bitmapで指定
         * */
        public void Set (int miplevel, int x, int y, int srcX, int srcY, int width, int height, Bitmap image) {
            if (image == null) {
                throw new ArgumentNullException ("Image is null");
            }
            if (Mutable == false) {
                throw new InvalidOperationException ("This Image2D is immutable");
            }
            if ((Option & NoMipmap) != 0 && miplevel != 0) {
                throw new ArgumentException ("MipmaplLevel is specified to NoMipmap image");
            }
            if (miplevel < 0 || miplevel > MaxMipLevel) {
                throw new ArgumentException ("Miplevel must be within [0," + MaxMipLevel + "]");
            }
            if (x < 0 || x + width > Width || y < 0 || y + height > Height) {
                throw new ArgumentException ("Size is invalid, x=" + x + ",y=" + y + ",width=" + width + ",height=" + height);
            }
            if (x + width > MiplevelWidth(miplevel) || y + height > MiplevelHeight(miplevel)) {
                throw new ArgumentException ("Region of specified miplevel is invalid, x=" + x + ",y=" + y + ",width=" + width + ",height=" + height); 
            }
            if (srcX + width > image.Width || srcY + height > image.Height) {
                throw new ArgumentException ("Specified region of Image is invalid, srcX=" + srcX + ",srcY=" + srcY + ",width=" + width + ",height=" + height);
            }

            var pixels =  ConvertToPixels (Format, srcX, srcY, width, height, image);

            SetPixels (0, miplevel, x, y, width, height, pixels);
        }

        public void Save (String fileName) {
            if (fileName == null) {
                throw new ArgumentNullException ("FileName is null");
            }
            if (fileName == "") {
                throw new ArgumentNullException ("FileName is empty");
            }

            base.Save (0, 0, fileName);
        }

        public void SaveMipmap (String fileName) {
            if (fileName == null) {
                throw new ArgumentNullException ("FileName is null");
            }
            if (fileName == "") {
                throw new ArgumentNullException ("FileName is empty");
            }

            base.SaveMipmapPyramid (0, fileName);
        }

        public override Object3D Duplicate () {
            return new Image2D (this);
        }


        #endregion

    }
}
